/*
	The Shadow Framework - a complete framework for Real Time Graphics based on OpenGL
    Copyright (C) 2008  Alessandro Martinelli  <alessandro.martinelli@unipv.it>

    This file is part of The Shadow Framework.

    The Shadow Framework is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    The Shadow Framework is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Shadow Framework.  If not, see <http://www.gnu.org/licenses/>.
*/

package objLoader.math;

import java.util.ArrayList;

public abstract class NonLinearMeanSquareProblem {

	private int dimension;
	private float[] sol;
	private ArrayList<float[]> inputs=new ArrayList<float[]>();
	private static float PRECISION=0.0001f;
	
	public NonLinearMeanSquareProblem(int dimension) {
		super();
		this.dimension=dimension;
		sol=new float[dimension];
	}
	
	public abstract float getStartingValue(int index);
	public abstract float getValue(float[] parameters,float[] input);
	public abstract float getDerived(float[] parameters,float[] input,int i);
	
	
	public void addInput(float input[]){
		inputs.add(input);
	}
	
	public void solve(){
		
		float tSol[]=new float[dimension];
		for(int i=0;i<dimension;i++){
			tSol[i]=getStartingValue(i);
		}
		
		int nCases=inputs.size();
		Object[] inputs=this.inputs.toArray();
		
		LinearSystem system=new LinearSystem(dimension);

		float u=5*nCases;
		
		int mn=0;
		float err=0;
		for(int i=0;i<nCases;i++){
			float val=getValue(tSol, ((float[])inputs[i]));
			err+=val*val;
		}
		while(err>PRECISION*PRECISION){
			
			float J[]=new float[dimension*nCases];
			float f[]=new float[nCases];
			
			for(int i=0;i<nCases;i++){
				for(int j=0;j<dimension;j++){
					J[dimension*i+j]=getDerived(tSol, ((float[])inputs[i]), j);
				}
				f[i]=getValue(tSol, ((float[])inputs[i]));
			}	
			for(int i=0;i<dimension;i++){
				for(int j=i;j<dimension;j++){
					float sum=0;
					for(int k=0;k<nCases;k++){
						sum+=J[dimension*k+i]*J[dimension*k+j];
					}
					if(i==j){
						system.setA(i, i, sum+u);
					}else{
						system.setA(i, j, sum);
						system.setA(j, i, sum);
					}
				}
				float sum=0;
				for(int k=0;k<nCases;k++){
					sum+=f[k]*J[dimension*k+i];
				}
				system.setb(i, sum);
			}	
			
			system.solveSystem();
			
			float h[]=system.getSol();
			
			float solT[]=new float[dimension];
			float solTT[]=new float[dimension];
			for(int i=0;i<dimension;i++)
				solTT[i]=tSol[i]+2*h[i];
			for(int i=0;i<dimension;i++)
				solT[i]=tSol[i]+h[i];
			
//			float v1=0;
//			float v2=0;
//			float v3=0;

//			for(int i=0;i<nCases;i++){
//				float val1=getValue(tSol, ((float[])inputs[i]));
//				float val2=getValue(solT, ((float[])inputs[i]));
//				float val3=getValue(solTT, ((float[])inputs[i]));
//				v1+=val1*val1;
//				v2+=val2*val2;
//				v3+=val3*val3;
//				
//			}
			
//			float errP=0;
//			float t=((v3-v1)-4*(v2-v1))/(4*(v3-v1)-8*(v2-v1));
//			
//			System.out.println("v1 "+v1+" v2 "+v2+" v3 "+v3);
//			if(!(t>=0 && t<=1)){
//				if(v2<v1 && v2<v3){
//					t=0.5f;
//				}else if(v3<v1 && v3<v2){
//					t=1;
//				}else{
//					errP=0;
//					t=-1;
//				}
//			}
//			System.out.println("t ");
//			if(t!=-1){
//				for(int i=0;i<dimension;i++)
//					tSol[i]+=h[i]*t*10;
//				for(int i=0;i<nCases;i++){
//					float val1=getValue(tSol, ((float[])inputs[i]));
//					errP+=val1*val1;
//				}
//			}
			
			
//			//Vediamo possibilmente di avere
			for(int i=0;i<dimension;i++){
				tSol[i]-=h[i];
			}
			
			float errP=0;
			for(int i=0;i<nCases;i++){
				
				float val=getValue(tSol, ((float[])inputs[i]));
				//System.out.println("Intraprendo val "+val+" par "+tSol[0]+" "+tSol[1]+" "+tSol[2]+" inp "+inputs[0]+" "+inputs[1]);
				errP+=val*val;
			}
			
			for(int i=0;i<dimension;i++){
				tSol[i]-=9*h[i];
			}
			
			float errP1=0;
			for(int i=0;i<nCases;i++){
				
				float val=getValue(tSol, ((float[])inputs[i]));
				//System.out.println("Intraprendo val "+val+" par "+tSol[0]+" "+tSol[1]+" "+tSol[2]+" inp "+inputs[0]+" "+inputs[1]);
				errP1+=val*val;
			}
			
			
			if(errP1<errP){
				err=errP1;
			}else{
				
				for(int i=0;i<dimension;i++){
					tSol[i]-=90*h[i];
				}
				float errP2=0;
				for(int i=0;i<nCases;i++){
					
					float val=getValue(tSol, ((float[])inputs[i]));
					//System.out.println("Intraprendo val "+val+" par "+tSol[0]+" "+tSol[1]+" "+tSol[2]+" inp "+inputs[0]+" "+inputs[1]);
					errP2+=val*val;
				}
				
				if(errP2<errP){
					errP=errP2;
				}else{
					
					float errP3=0;
					
					float dev=0;
					for(int l=0;l<dimension;l++){
						for(int i=0;i<nCases;i++){	
							float val=getValue(tSol, ((float[])inputs[i]));
							float dv=getDerived(tSol, ((float[])inputs[i]), l);
							//System.out.println("Intraprendo val "+val+" par "+tSol[0]+" "+tSol[1]+" "+tSol[2]+" inp "+inputs[0]+" "+inputs[1]);
							errP2+=2*val*dv*h[l];
						}
					}
					
					float step=1;
					if(dev!=0){
						step=1.0f-(err/dev);
					}
					
					for(int i=0;i<dimension;i++){
						tSol[i]+=100*h[i]-step*h[i];
					}

					for(int i=0;i<nCases;i++){
						
						float val=getValue(tSol, ((float[])inputs[i]));
						//System.out.println("Intraprendo val "+val+" par "+tSol[0]+" "+tSol[1]+" "+tSol[2]+" inp "+inputs[0]+" "+inputs[1]);
						errP3+=val*val;
					}
					if(errP3<errP){
						errP=errP3;
					}else{
						for(int i=0;i<dimension;i++){
							tSol[i]+=step*h[i]-h[i];
						}
					}
				}
			}
			
			//float rap=errP/(err==0?1:err);
			
			u*=0.9f;
			
			if(err<=errP)
				errP=0;
			else
				for(int i=0;i<dimension;i++)
					sol[i]=tSol[i];
			err=errP;
			
//			for(int i=0;i<dimension;i++){
//				System.out.println("H is here "+h[i]);
//			}
			System.out.println("Errp "+errP+" "+u);
			
			for(int i=0;i<dimension;i++){
				System.out.println("Sol is "+tSol[i]+" "+i);
			}
			
			mn++;
			if(mn==300)
				err=0; 
		}
//		for(int i=0;i<dimension;i++){
//			System.out.println("Last Chahnce Sol is "+tSol[i]+" "+i);
//		}
//		for(int i=0;i<dimension;i++){
//			for(int l=0;l<nCases;l++){
//				System.out.println("  der "+i+" "+l+" "+getDerived(tSol, ((float[])inputs[l]), i));
//			}
//		}
		
		System.out.println("mn "+mn);
	}

	public float[] getSol() {
		return sol;
	}

	public void setSol(float[] sol) {
		this.sol = sol;
	}
	
	
}
